package cn.org.hentai.jtt1078.http;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.org.hentai.jtt1078.entity.Media;
import cn.org.hentai.jtt1078.publisher.PublishManager;
import cn.org.hentai.jtt1078.server.Session;
import cn.org.hentai.jtt1078.subscriber.Subscriber;
import cn.org.hentai.jtt1078.util.FileUtils;
import cn.org.hentai.jtt1078.util.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;

/**
 * Created by matrixy on 2019/8/13.
 */
public class NettyHttpServerHandler extends ChannelInboundHandlerAdapter
{
    static Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class);
    static final byte[] HTTP_403_DATA = "<h1>403 Forbidden</h1><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding-->".getBytes();
    static final String HEADER_ENCODING = "ISO-8859-1";
    private static final String WEBSOCKET_PATH = "/websocket/video/flv/";
    private WebSocketServerHandshaker handshaker;

    private static final AttributeKey<Session> SESSION_KEY = AttributeKey.valueOf("session");

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception
    {
        if (!(msg instanceof FullHttpRequest)) {
            return;
        }
        FullHttpRequest fhr = (FullHttpRequest) msg;
        String uri = fhr.uri();
        Packet resp = Packet.create(1024);
        // uri的第二段，就是通道标签
        if (uri.startsWith("/video/"))
        {
            String tag = uri.substring("/video/".length());

            resp.addBytes("HTTP/1.1 200 OK\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("Connection: keep-alive\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("Content-Type: video/x-flv\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("Transfer-Encoding: chunked\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("Cache-Control: no-cache\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("Access-Control-Allow-Origin: *\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("Access-Control-Allow-Credentials: true\r\n".getBytes(HEADER_ENCODING));
            resp.addBytes("\r\n".getBytes(HEADER_ENCODING));

            ctx.writeAndFlush(resp.getBytes()).await();

            // 订阅视频数据
            long wid = PublishManager.getInstance().subscribe(tag, Media.Type.Video, ctx).getId();
            setSession(ctx, new Session().set("subscriber-id", wid).set("tag", tag));
            return;
        }
        if (uri.equals("/test/multimedia"))
        {
            responseHTMLFile("/multimedia.html", ctx);
            return;
        }
        
        if (uri.startsWith("/static/")) {
            responseHTMLFile(uri.substring("/static".length()), ctx);
            return;
        }
        
        // websocket handle
        if (uri.startsWith(WEBSOCKET_PATH)) {
            startWebsocket(ctx, fhr);
            return;
        }
        
        // other 403
        http403(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception
    {
        super.channelInactive(ctx);
        Session session = getSession(ctx);
        if (session != null && session.has("subscriber-id") && session.has("tag"))
        {
            String tag = session.get("tag");
            Long wid = session.get("subscriber-id");
            PublishManager.getInstance().unsubscribe(tag, wid);
        }
    }
    
    private void http403(ChannelHandlerContext ctx) throws InterruptedException {
        ByteBuf body = Unpooled.buffer(HTTP_403_DATA.length);
        body.writeBytes(HTTP_403_DATA);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(403), body);
        response.headers().add("Content-Length", HTTP_403_DATA.length);
        ctx.writeAndFlush(response).await();
        ctx.flush();
    }

    // 响应静态文件内容
    private void responseHTMLFile(String htmlFilePath, ChannelHandlerContext ctx)
    {
        logger.info("htmlFilePath: {}", htmlFilePath);
        byte[] fileData = FileUtils.read(NettyHttpServerHandler.class.getResourceAsStream(htmlFilePath));
        ByteBuf body = Unpooled.buffer(fileData.length);
        body.writeBytes(fileData);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(200), body);
        response.headers().add("Content-Length", fileData.length);
        ctx.write(response);
        ctx.flush();
    }
    
    
    private void startWebsocket(ChannelHandlerContext ctx, FullHttpRequest req) throws InterruptedException {
        logger.info("websocket: {}", req.uri());
        
        String tag = req.uri().substring(WEBSOCKET_PATH.length());
        if (StringUtils.isBlank(tag)) {
            // no tag, usage: {WEBSOCKET_PATH}{sim}-{channel}
            // eg: /websocket/video/flv/013012341234-3
            http403(ctx); 
            return;
        }
        
        // Handshake
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), null, true);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            ChannelFuture cf = handshaker.handshake(ctx.channel(), req);
            if (cf.isSuccess()) {
                long wid = PublishManager.getInstance().subscribe(tag, Media.Type.Video, ctx, Subscriber.Type.WEBSOCKET).getId();
                setSession(ctx, new Session().set("subscriber-id", wid).set("tag", tag));
            }
        }
    }
    
    private static String getWebSocketLocation(FullHttpRequest req) {
        String location = req.headers().get(HttpHeaderNames.HOST) + WEBSOCKET_PATH;
        return "ws://" + location;
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception
    {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
    {
        ctx.close();
        cause.printStackTrace();
    }

    public final void setSession(ChannelHandlerContext context, Session session)
    {
        context.channel().attr(SESSION_KEY).set(session);
    }

    public final Session getSession(ChannelHandlerContext context)
    {
        Attribute<Session> attr = context.channel().attr(SESSION_KEY);
        if (null == attr) return null;
        else return attr.get();
    }
}

